package visual

import (
	"cgs-server/helper"
	file2 "cgs-server/pkg/file"
	"cgs-server/pkg/set"
	"cgs-server/server"
	"cgs-server/server/assets/cgs"
	"cgs-server/server/assets/cgs_bim"
	"cgs-server/server/assets/cgs_map"
	"cgs-server/server/assets/cgs_portal"
	"cgs-server/server/assets/cgs_portal/category"
	"cgs-server/server/assets/cgs_worksheet"
	"cgs-server/server/assets/earth"
	"cgs-server/server/assets/scene"
	"cgs-server/server/base"
	"cgs-server/server/cgs/app/service"
	"cgs-server/server/cgs/app/store/mongo"
	"cgs-server/server/model"
	"cgs-server/server/system"
	"cgs-server/server/system/role"
	"errors"
	"fmt"
	"github.com/aliyun/aliyun-oss-go-sdk/oss"
	uuid "github.com/satori/go.uuid"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/bson/primitive"
	"io"
	"io/ioutil"
	"mime/multipart"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"
)

type VisualService interface {
	//List() []*Visual
	Add(mdl *model.DataUserModel) error
	// id: 视图id
	Export(id primitive.ObjectID, visualType string, user *system.User) ([]*model.ZipFileData, error)
	// 级联选择器
	Cascade(visualType string, getAllWorksheet bool, user *model.User) ([]*VisualCascade, error)
	// 导入数据
	Import(file *multipart.FileHeader, filePath, category, visualType, serverHost string, user *model.User) error
	// 上传文件到 oss
	PutFileToOss(file *multipart.FileHeader, mdl *model.DataUserModel) (*PutFileResult, error)

	// 上传文件到服务器
	PutFileToServer(file *multipart.FileHeader, mdl *model.DataUserModel) (*PutFileResult, error)

	base.Service

	// 获取所有应用的所有权限
	GetAllVisualAuths(tenantId string) (map[string][]string, error)

	// 检查应用的应用角色信息，没有则添加一个
	CheckVisual(user *model.User) error

	// 根据 projectId 获取视图
	GetByProjectID(mdl *model.DataUserModel) (interface{}, error)

	// 查找当前角色下所有的应用，并以组织分类
	ListAll(user *model.User) ([]*category.Vo, error)
}

func NewVisualService() VisualService {
	return &visualService{
		visualRepo:  NewVisualRepository(),
		configRepo:  NewConfigRepository(),
		cgsFileRepo: NewCgsFileRepository(),
	}
}

type visualService struct {
	visualRepo  VisualRepository
	configRepo  ConfigRepository
	cgsFileRepo CgsFileRepository

	base.ServiceImpl
}

func (v *visualService) Add(mdl *model.DataUserModel) error {
	if visualModel, ok := mdl.Data.(*Model); ok {
		visualId := visualModel.Visual.ID
		if visualId.IsZero() {
			visualId = primitive.NewObjectID()
		}
		projectId, err := primitive.ObjectIDFromHex(strings.TrimSpace(visualModel.Visual.ProjectID))
		if err != nil && cgs_portal.HasProject(visualModel.VisualType) {
			return errors.New("ProjectId can not be empty!")
		}
		configId := visualModel.Config.ID
		if configId.IsZero() {
			configId = primitive.NewObjectID()
		}

		if visualModel.VisualType == "" {
			return errors.New("VisualType is not allowed empty!")
		}
		if _, ok := cgs_portal.VisualTypes[visualModel.VisualType]; !ok {
			return errors.New("VisualType is not existed.")
		}

		// 是否公开
		if visualModel.Visual.Status == nil {
			status := int32(1)
			visualModel.Visual.Status = &status
		}

		// 集合名
		visualCollectionName := server.CgsPortalVisualCollectionName + visualModel.VisualType
		visualConfigCollectionName := server.CgsPortalVisualCollectionName + visualModel.VisualType + "Config"

		// 设置ObjectID的字段值
		visualModel.Visual.IdObjID = visualId
		visualModel.Visual.ID = visualId
		visualModel.Visual.ProjectObjID = projectId

		visualModel.Config.IdObjID = configId
		visualModel.Config.VisualId = visualId
		visualModel.Config.ID = configId

		// 新增视图
		_, err = v.visualRepo.Add(&visualModel.Visual, visualCollectionName, mdl.User)
		if err != nil {
			return err
		}
		// 新增分类
		_, err = v.configRepo.Add(&visualModel.Config, visualConfigCollectionName, mdl.User)
		if err != nil {
			return err
		}

		// 添加该应用的管理员角色
		sort := 0
		roleModel := &model.Role{
			RoleName:  "应用管理员",
			ParentId:  "0",
			Sort:      &sort,
			RoleAlias: "AppAdministrator",
			Desc:      "可以配置应用，管理应用下所有数据和人员",
			Type:      model.AppRole,
			VisualId:  visualId.Hex(),
		}
		roleService := role.NewRoleService()
		duModel := &model.DataUserModel{
			Data: roleModel,
			User: mdl.User,
		}
		err = roleService.Save(duModel)
		if err != nil {
			return err
		}

		// 更新用户角色关联表
		roleModel.UserIds = []string{mdl.User.GetId()}
		err = roleService.UpdateUser(duModel)
		if err != nil {
			return err
		}
		return nil
	} else {
		return errors.New("VisualModel type error")
	}
}

func (v *visualService) Export(id primitive.ObjectID, visualType string, user *system.User) ([]*model.ZipFileData, error) {
	// 集合名
	visualCollectionName := server.CgsPortalVisualCollectionName + visualType
	visualConfigCollectionName := server.CgsPortalVisualCollectionName + visualType + "Config"
	// 创建数据库连接
	err := v.visualRepo.CreateDB(visualCollectionName)
	if err != nil {
		return nil, err

	}
	err = v.configRepo.CreateDB(visualConfigCollectionName)
	if err != nil {
		return nil, err
	}

	// 查询 visual 和 config
	visual, err := v.visualRepo.FindById(id)
	if err != nil {
		return nil, err
	}
	config, err := v.configRepo.FindById(id)
	if err != nil {
		return nil, err
	}

	// 配置 导出的字段,不用的字段不导出
	visualData := bson.M{
		"ID":            visual.ID,
		"Title":         visual.Title,
		"BackgroundUrl": visual.BackgroundUrl,
		"Category":      visual.Category,
		"Password":      "",
		"Status":        visual.Status,
		"ProjectID":     visual.ProjectID,
	}
	configData := bson.M{
		"ID":        config.ID,
		"VisualId":  config.VisualId,
		"Detail":    config.Detail,
		"Component": config.Component,
		"SceneUrl":  config.SceneUrl,
	}

	/*model := Model{
		Visual:     *visual,
		Config:     *config,
	}*/
	mdl := bson.M{
		"Visual": visualData,
		"Config": configData,
	}

	var zipData []*model.ZipFileData
	res, err := helper.ToJSON(mdl)
	if err != nil {
		return nil, err
	}
	if len(res) > 0 {
		zipData = append(zipData, &model.ZipFileData{
			Name: server.ExportPortalFile + "visual.json",
			Body: res,
		})
	} else {
		return zipData, errors.New("portal数据不存在！")
	}
	// 背景图片
	if visual.BackgroundUrl != "" {
		ext := filepath.Ext(visual.BackgroundUrl)
		var imageBytes []byte
		if strings.HasPrefix(visual.BackgroundUrl, "http") {
			imageBytes, err = helper.Get(visual.BackgroundUrl)
		} else {
			// 读取文件
			imageBytes, err = os.ReadFile(server.MapPath(visual.BackgroundUrl))
		}

		if err == nil && len(imageBytes) > 0 {
			zipData = append(zipData, &model.ZipFileData{
				Name: server.ExportPortalFile + "background" + ext,
				Body: imageBytes,
			})
		}
	}

	//-------------studio-------------
	var cgsProjId primitive.ObjectID
	var configFormIdSet = set.NewSet()
	if visualType != string(cgs_portal.Workflow) {
		cgsService := cgs.NewCgsService()
		cgsProjId, err = primitive.ObjectIDFromHex(visual.ProjectID)
		if err != nil {
			return nil, err
		}
		studioData, cfs, err := cgsService.Export(cgsProjId)
		if err != nil {
			return nil, err
		}
		configFormIdSet = cfs
		zipData = append(zipData, studioData...)
	}

	sceneFormIdSet := set.NewSet()
	//--------------editor--------------
	if visualType == string(cgs_portal.ThreeD) {
		sceneService := scene.NewSceneService()
		// 场景id
		editorData, err := sceneService.Export(cgsProjId)
		if err != nil {
			return nil, err
		}
		zipData = append(zipData, editorData...)
	} else if visualType == string(cgs_portal.Earth) {
		sceneService := earth.NewEarthService()
		// 场景id
		editorData, formSet, err := sceneService.Export(cgsProjId)
		sceneFormIdSet = formSet
		if err != nil {
			return nil, err
		}
		zipData = append(zipData, editorData...)
	} else if visualType == string(cgs_portal.TwoD) {
		sceneService := cgs_map.NewMapService()
		editorData, err := sceneService.Export(cgsProjId)
		if err != nil {
			return nil, err
		}
		zipData = append(zipData, editorData...)
	}

	//--------------form-----------------
	formService := cgs_worksheet.NewWorksheetService()

	formData, err := formService.Export(visual.ID.Hex(), configFormIdSet.Union(sceneFormIdSet), user)
	if err != nil {
		return nil, err
	}
	zipData = append(zipData, formData...)

	return zipData, nil

}

func (v *visualService) Cascade(visualType string, getAllWorksheet bool, user *model.User) ([]*VisualCascade, error) {
	if user == nil {
		return nil, errors.New("Permission denied.")
	}
	if visualType == "" {
		return nil, errors.New("VisualType is required.")
	}

	// 集合名
	visualCollectionName := server.CgsPortalVisualCollectionName + visualType
	// 创建数据库连接
	err := v.visualRepo.CreateDB(visualCollectionName)
	if err != nil {
		return nil, err

	}

	categroyService := category.NewCategoryService()
	err = categroyService.Init()
	if err != nil {
		return nil, err
	}
	categoryList, err := categroyService.ListAll(visualType, user)
	if err != nil {
		return nil, err
	}

	categoryList = append(categoryList, &category.Vo{
		CategoryKey:   "默认",
		CategoryValue: "default",
	})

	if len(categoryList) == 0 {
		//return nil, errors.New("No category.")
		return []*VisualCascade{}, nil
	}

	filter := bson.M{}
	if user.RoleName == "AppAdministrator" || user.RoleName == "Administrator" {
		// 可以获取所有应用
		//filter = &bson.M{
		//	"Category": visualModel.Visual.Category,
		//}
	} else {
		ids := []primitive.ObjectID{}
		if len(user.AppAuths) == 0 {
		}
		for k := range user.AppAuths {
			bsonId, _ := primitive.ObjectIDFromHex(k)
			ids = append(ids, bsonId)
		}
		filter = bson.M{
			//"Category": visualModel.Visual.Category,
			"ID": bson.M{
				"$in": ids,
			},
		}
	}

	var vcList []*VisualCascade
	for _, c := range categoryList {
		visual := &Visual{}
		if c.CategoryValue == "default" {
			filter["Category"] = bson.M{
				"$in": bson.A{nil, -1},
			}
		} else {
			cv, err := strconv.ParseInt(c.CategoryValue, 10, 32)
			cv2 := int32(cv)
			if err != nil {
				return nil, err
			}
			visual.Category = &cv2
			filter["Category"] = cv
		}
		duModel := &model.DataUserModel{
			Data:   visual,
			User:   user,
			Filter: &filter,
		}
		visualList, err := v.visualRepo.FindByCategory(duModel)
		if err != nil {
			return nil, err
		}
		visualCascade := &VisualCascade{
			Value: c.CategoryValue,
			Label: c.CategoryKey,
		}
		for _, v := range visualList {
			// 如果visualType == App, 则查询下面的所有表单
			var vc = &VisualCascade{
				Value: v.ID.Hex(),
				Label: v.Title,
			}
			if visualType == "App" && getAllWorksheet {
				storeIns, _ := mongo.GetMongoFactory()
				appService := service.NewService(storeIns)
				infoList, err := appService.WorksheetInfo().ListByAppId(v.GetId(), []string{}, user)
				if err != nil {
					return nil, err
				}
				for _, info := range *infoList {
					vc.Children = append(vc.Children, &VisualCascade{
						Value: info.Id.Hex(),
						Label: info.Name,
					})
				}
			}

			visualCascade.Children = append(visualCascade.Children, vc)
		}
		if len(visualCascade.Children) > 0 {
			vcList = append(vcList, visualCascade)
		}
	}

	return vcList, nil
}

func (v *visualService) Import(file *multipart.FileHeader, filePath, category, visualType, serverHost string, user *model.User) error {
	portalArr, projectArr, editorArr, formArr, err := getFileData(file, filePath)
	if err != nil {
		return err
	}
	if (len(portalArr) == 0 || len(editorArr) == 0 || len(projectArr) == 0) && visualType != string(cgs_portal.Workflow) {
		return errors.New("protal、editor、studio数据为空")
	}
	cgy, err := strconv.Atoi(category)
	if err != nil {
		cgy = -1
	}
	cgy2 := int32(cgy)
	visualId := primitive.NewObjectID()
	//fmt.Println(portalArr, projectArr, editorArr, formArr)

	// 1. 添加 form 表单，传入categoryId 和 visualId，并导入相应数据
	formService := cgs_worksheet.NewWorksheetService()
	// todo categoryId 需要修改
	idData, err := formService.Import(formArr, visualType, category, visualId.Hex(), user)
	if err != nil {
		return err
	}
	//fmt.Println(idData)

	// 2. 添加 scene，返回sceneId
	sceneID := ""
	if visualType == string(cgs_portal.ThreeD) {
		sceneService := scene.NewSceneService()
		sceneID, err = sceneService.Import(editorArr, serverHost, user)
		if err != nil {
			return err
		}
	} else if visualType == string(cgs_portal.Earth) {
		sceneService := earth.NewEarthService()
		sceneID, err = sceneService.Import(editorArr, *idData, user)
		if err != nil {
			return err
		}
	} else if visualType == string(cgs_portal.TwoD) {
		sceneService := cgs_map.NewMapService()
		sceneID, err = sceneService.Import(editorArr, user)
		if err != nil {
			return err
		}
	}

	// 3. 添加 project， 传入生成好的 publishId (visualId)，返回 projectId
	cgsService := cgs.NewCgsService()
	projectId, err := cgsService.Import(projectArr, sceneID, visualId.Hex(), idData, user)
	if err != nil {
		return err
	}

	// 4. 添加 visual
	mdl := &Model{
		VisualType: visualType,
	}
	for _, value := range portalArr {
		name := value.Name
		if strings.HasSuffix(name, "visual.json") {
			_ = helper.FromJSON(value.Body, &mdl)
			mdl.Visual.ID = visualId
			//mdl.Visual.Title += "_copy"
			mdl.Visual.IdObjID = visualId
			mdl.Visual.Category = &cgy2
			mdl.Visual.Password = ""
			mdl.Visual.UserId = user.UserId.Hex()

			mdl.Visual.ProjectID = projectId
			bsonId, _ := primitive.ObjectIDFromHex(projectId)
			mdl.Visual.ProjectObjID = bsonId

			mdl.Config.IdObjID = primitive.NewObjectID()
			mdl.Config.ID = mdl.Config.IdObjID
			mdl.Config.VisualId = mdl.Visual.ID
			mdl.Config.UserId = user.UserId.Hex()

			for _, value := range portalArr {
				if strings.Contains(value.Name, "background") {
					fileExt := filepath.Ext(value.Name)
					// 背景图片
					v4, err := uuid.NewV4()
					if err != nil {
						return err
					}
					u1 := v4.String()
					u1 = strings.ReplaceAll(u1, "-", "")
					newFileName := u1 + fileExt
					savePath := fmt.Sprintf("/Upload/File/%v", helper.TimeToString(time.Now(), "yyyyMMdd"))
					physicalPath := server.MapPath(savePath)

					err = file2.WriteFileWithBytes(physicalPath, newFileName, value.Body)
					if err == nil {
						mdl.Visual.BackgroundUrl = savePath + "/" + newFileName
					}
				}
			}

			// 添加到数据库
			err := v.Add(&model.DataUserModel{
				Data: mdl,
				User: user,
			})
			if err != nil {
				return err
			}
		}
	}

	return nil
}

func getFileData(file *multipart.FileHeader, filePath string) (portalArr, projectArr,
	editorArr, formArr []*model.ZipFileData, err error) {
	var jsonFile []byte
	if file != nil {
		f, err := file.Open()
		if err != nil {
			return nil, nil, nil, nil, err
		}
		defer f.Close()
		jsonFile, err = ioutil.ReadAll(f)
		if err != nil {
			return nil, nil, nil, nil, err
		}
	} else if filePath == "" {
		err = errors.New("filePath or file is required")
		return nil, nil, nil, nil, err
	} else {
		body, err := helper.Get(filePath)
		if err != nil {
			return nil, nil, nil, nil, err
		}
		jsonFile = body
	}
	if len(jsonFile) == 0 {
		err = errors.New("file data is empty")
		return
	}

	zipData, err := base.ReadZipFile(jsonFile)
	if err != nil {
		return nil, nil, nil, nil, err
	}

	//result := TraverseZipFileData(zipData)
	for _, value := range zipData {
		name := value.Name
		file := name[:strings.Index(name, "/")] + "/"
		if file == server.ExportPortalFile {
			portalArr = append(portalArr, value)
		} else if file == server.ExportCgsFile {
			projectArr = append(projectArr, value)
		} else if file == server.ExportEditorFile {
			editorArr = append(editorArr, value)
		} else if file == server.ExportFormFile {
			formArr = append(formArr, value)
		}
	}
	return
}

// 更新视图
func (v *visualService) Update(mdl *model.DataUserModel) error {
	if visualModel, ok := mdl.Data.(*Model); ok {
		// 集合名
		visualCollectionName := server.CgsPortalVisualCollectionName + visualModel.VisualType
		visualConfigCollectionName := server.CgsPortalVisualCollectionName + visualModel.VisualType + "Config"

		//visualId, err := primitive.ObjectIDFromHex(strings.TrimSpace(mdl.Visual.ID))
		visualId := visualModel.Visual.ID
		if visualId.IsZero() {
			//visualId, err = primitive.ObjectIDFromHex(strings.TrimSpace(mdl.Config.VisualId))
			visualId = visualModel.Config.VisualId
		}
		if visualId.IsZero() {
			return errors.New("ID is not allowed to be empty.")
		}

		// 更新 visual
		v.visualRepo.SetCollectionName(visualCollectionName)
		err := v.visualRepo.Update(mdl)
		if err != nil {
			return err
		}
		// 更新 config
		v.configRepo.SetCollectionName(visualConfigCollectionName)
		err = v.configRepo.Update(mdl)
		if err != nil {
			return err
		}
		return nil
	} else {
		return errors.New("VisualModel type error")
	}
}

// 删除视图
func (v *visualService) Remove(mdl *model.DataUserModel) error {
	if visualModel, ok := mdl.Data.(*Model); ok {
		// 集合名
		visualCollectionName := server.CgsPortalVisualCollectionName + visualModel.VisualType
		visualConfigCollectionName := server.CgsPortalVisualCollectionName + visualModel.VisualType + "Config"

		visualId := visualModel.Visual.ID
		if visualId.IsZero() {
			return errors.New("ID is not allowed to be empty.")
		}

		// 查询 Visual
		v.visualRepo.SetCollectionName(visualCollectionName)
		one, err := v.visualRepo.FindOne(mdl)
		if err != nil {
			return err
		}
		if one == nil {
			return model.NotFoundError("visual")
		}
		visualResult := one.(*Visual)

		// 删除 visual
		err = v.visualRepo.Delete(mdl)
		if err != nil {
			return err
		}
		// 删除 config
		v.configRepo.SetCollectionName(visualConfigCollectionName)
		err = v.configRepo.Delete(mdl)
		if err != nil {
			return err
		}

		// 删除应用角色及角色权限关联表
		roleService := role.NewRoleService()
		err = roleService.RemoveByVisualId(&model.DataUserModel{
			Data: &model.Role{VisualId: visualId.Hex()},
			User: mdl.User,
		})
		if err != nil {
			return err
		}

		if cgs_portal.HasProject(visualModel.VisualType) {
			// 删除cgsproject
			projId, err := primitive.ObjectIDFromHex(visualResult.ProjectID)
			if err != nil {
				return err
			}
			cgsProj := &cgs.CgsProj{
				ID: projId,
			}
			cgsService := cgs.NewCgsService()
			err = cgsService.Remove(&model.DataUserModel{
				Data: cgsProj,
				User: mdl.User,
			})
			if err != nil {
				return err
			}

			// 删除scene
			sceneId, err := cgsProj.GetSceneId()
			if err != nil {
				return err
			}
			sceneBsonId, err := primitive.ObjectIDFromHex(sceneId)
			if err != nil {
				return err
			}
			if cgsProj.Type == "Earth" {
				earthService := earth.NewEarthService()
				err = earthService.RemoveLogic(&model.DataUserModel{
					Data: &earth.Earth{
						ID: sceneBsonId,
					},
					User: mdl.User,
				})
			} else if cgsProj.Type == "TwoD" {
				cgsMapService := cgs_map.NewMapService()
				err = cgsMapService.RemoveLogic(&model.DataUserModel{
					Data: &cgs_map.CgsMap{
						Id: sceneBsonId,
					},
					User: mdl.User,
				})
			} else if cgsProj.Type == "Bim" {
				bimService := cgs_bim.NewBimService()
				err = bimService.RemoveLogic(&model.DataUserModel{
					Data: &earth.Earth{
						ID: sceneBsonId,
					},
					User: mdl.User,
				})
			} else {
				sceneService := scene.NewSceneService()
				err = sceneService.RemoveLogic(&model.DataUserModel{
					Data: &scene.Model{
						ID: sceneBsonId,
					},
					User: mdl.User,
				})
			}
		}
		return err
	} else {
		return errors.New("VisualModel type error")
	}
}

// 上传文件
func (v *visualService) PutFileToOss(file *multipart.FileHeader, mdl *model.DataUserModel) (*PutFileResult, error) {
	endpoint := "oss-cn-beijing.aliyuncs.com"
	bucketName := "cgscloud2"
	accessKey := "LTAI4FesspMeJC5xMpLxiYyU"
	secretKey := "G0HuDMJ6XyWv0lzys31F0xfXOwvfBf"

	client, err := oss.New(endpoint, accessKey, secretKey)
	if err != nil {
		return nil, err
	}
	// 判断存储空间是否存在。
	/*isExist, err := client.IsBucketExist(bucketName)
	if err != nil {
		fmt.Println("Error:", err)
		os.Exit(-1)
	}
	fmt.Println("IsBucketExist result : ", isExist)*/

	// 获取存储空间。
	bucket, err := client.Bucket(bucketName)
	if err != nil {
		return nil, err
	}
	fileName := file.Filename
	fileSize := file.Size
	fileType := file.Header.Get("Content-Type")
	fileExt := filepath.Ext(fileName)
	fileNameWithoutExt := strings.TrimSuffix(fileName, fileExt)

	cgsFile := &CgsFile{
		FileName:   fileName,
		FileSize:   fileSize,
		FileType:   fileType,
		Name:       fileNameWithoutExt,
		OriginName: fileName,
	}

	source, err := file.Open()
	if err != nil {
		return nil, err
	}
	defer source.Close()

	v4, err := uuid.NewV4()
	if err != nil {
		return nil, err
	}
	u1 := v4.String()
	u1 = strings.ReplaceAll(u1, "-", "")

	newFileName := u1 + fileExt

	savePath := fmt.Sprintf("upload/CgsPortal/%v", helper.TimeToString(time.Now(), "yyyyMMdd"))
	targetPath := fmt.Sprintf("http://%v.%v/%v/%v%v", bucketName, endpoint, savePath, u1, fileExt)

	//fmt.Println(targetPath)
	cgsFile.SavePath = savePath
	cgsFile.Url = targetPath

	// 上传文件流。
	// http://cgscloud2.oss-cn-beijing.aliyuncs.com/upload/001.png
	err = bucket.PutObject(savePath+"/"+newFileName, source)
	if err != nil {
		return nil, err
	}

	mdl.Data = cgsFile
	err = v.cgsFileRepo.SaveFile(mdl)
	if err != nil {
		return nil, err
	}
	result := &PutFileResult{
		Link:         targetPath,
		Domain:       endpoint,
		Name:         newFileName,
		OriginalName: fileName,
	}
	return result, nil
}

func (v *visualService) PutFileToServer(file *multipart.FileHeader, mdl *model.DataUserModel) (*PutFileResult, error) {
	if file == nil {
		return nil, errors.New("file is nil")
	}
	fileName := file.Filename
	fileSize := file.Size
	fileType := file.Header.Get("Content-Type")
	fileExt := filepath.Ext(fileName)
	fileNameWithoutExt := strings.TrimSuffix(fileName, fileExt)

	v4, err := uuid.NewV4()
	if err != nil {
		return nil, err
	}
	u1 := v4.String()
	u1 = strings.ReplaceAll(u1, "-", "")
	newFileName := u1 + fileExt

	cgsFile := &CgsFile{
		FileName:   newFileName,
		FileSize:   fileSize,
		FileType:   fileType,
		Name:       fileNameWithoutExt,
		OriginName: fileName,
	}

	// save file
	now := time.Now()

	savePath := fmt.Sprintf("/Upload/File/%v", helper.TimeToString(now, "yyyyMMdd"))
	physicalPath := server.MapPath(savePath)
	if _, err := os.Stat(physicalPath); os.IsNotExist(err) {
		_ = os.MkdirAll(physicalPath, 0755)
	}

	targetPath := fmt.Sprintf("%v/%v", physicalPath, newFileName)
	target, err := os.Create(targetPath)
	if err != nil {
		return nil, err
	}
	defer target.Close()

	source, err := file.Open()
	if err != nil {
		return nil, err
	}
	defer source.Close()

	//fmt.Println(targetPath)
	cgsFile.SavePath = savePath
	cgsFile.Url = fmt.Sprintf("%v/%v", savePath, newFileName)

	_, err = io.Copy(target, source)
	if err != nil {
		return nil, err
	}

	mdl.Data = cgsFile
	err = v.cgsFileRepo.SaveFile(mdl)
	if err != nil {
		return nil, err
	}
	result := &PutFileResult{
		Link:         cgsFile.Url,
		Domain:       "",
		Name:         newFileName,
		OriginalName: fileName,
	}
	return result, nil
}

// 视图详情
func (v *visualService) GetOne(mdl *model.DataUserModel) (interface{}, error) {
	if visualModel, ok := mdl.Data.(*Model); ok {
		// 集合名
		visualCollectionName := server.CgsPortalVisualCollectionName + visualModel.VisualType
		visualConfigCollectionName := server.CgsPortalVisualCollectionName + visualModel.VisualType + "Config"

		visualId := visualModel.Visual.ID
		if visualId.IsZero() {
			return nil, errors.New("ID is not allowed to be empty.")
		}

		// 查询 visual
		v.visualRepo.SetCollectionName(visualCollectionName)
		visualRes, err := v.visualRepo.FindOne(mdl)
		if err != nil {
			return nil, err
		}

		// 查询 config
		v.configRepo.SetCollectionName(visualConfigCollectionName)
		configRes, err := v.configRepo.FindOne(mdl)
		if err != nil {
			return nil, err
		}
		if visual, ok := visualRes.(*Visual); ok {
			if cgs_portal.HasProject(visualModel.VisualType) {
				cgsService := cgs.NewCgsService()
				proj, err := cgsService.GetByPublishId(visual.GetId(), mdl.User)
				if err != nil {
					return nil, err
				}
				sceneId, err := proj.GetSceneId()
				if err != nil {
					return nil, err
				}
				visual.SceneID = sceneId
				visual.SetKey()
			}
			visualModel.Visual = *visual
		}
		if config, ok := configRes.(*Config); ok {
			visualModel.Config = *config
		}
		return visualModel, nil
	} else {
		return nil, errors.New("VisualModel type error")
	}
}

// 根据 projectId 获取视图
func (v *visualService) GetByProjectID(mdl *model.DataUserModel) (interface{}, error) {
	if visualModel, ok := mdl.Data.(*Model); ok {
		// 集合名
		visualCollectionName := server.CgsPortalVisualCollectionName + visualModel.VisualType

		// 查询 visual
		v.visualRepo.SetCollectionName(visualCollectionName)
		mdl.Filter = &bson.M{
			"ProjectID": visualModel.Visual.ProjectID,
		}

		projBsonId, err := primitive.ObjectIDFromHex(visualModel.Visual.ProjectID)
		if err == nil {
			mdl.Filter = &bson.M{
				"$or": bson.A{
					bson.M{
						"ProjectID": visualModel.Visual.ProjectID,
					},
					bson.M{
						"ProjectID": projBsonId,
					},
				},
			}
		}

		visualRes, err := v.visualRepo.FindOne(mdl)
		if err != nil {
			return nil, err
		}

		if visual, ok := visualRes.(*Visual); ok {
			visualModel.Visual = *visual
		}

		return visualModel, nil
	} else {
		return nil, errors.New("VisualModel type error")
	}
}

// 视图列表
func (v *visualService) List(mdl *model.DataUserModel) (interface{}, error) {
	if visualModel, ok := mdl.Data.(*Model); ok {
		// 集合名
		visualCollectionName := server.CgsPortalVisualCollectionName + visualModel.VisualType
		//visualConfigCollectionName := server.CgsPortalVisualCollectionName + visualModel.VisualType + "Config"

		filter := bson.M{}
		if visualModel.Visual.Category == nil {
			// 获取所有应用
		} else if *visualModel.Visual.Category == -1 {
			// 查询所有分类
			categoryService := category.NewCategoryService()
			categoryList, err := categoryService.ListAll(visualModel.VisualType, mdl.User)
			if err != nil {
				return nil, err
			}
			categoryValues := []int32{}
			for i := range categoryList {
				v, err := strconv.ParseInt(categoryList[i].CategoryValue, 10, 64)
				if err != nil {
					return nil, err
				}
				if v == -1 {
					continue
				}
				categoryValues = append(categoryValues, int32(v))
			}
			filter = bson.M{
				"Category": bson.M{
					"$nin": categoryValues,
				},
			}
		}

		// 查询 visual
		v.visualRepo.SetCollectionName(visualCollectionName)
		if mdl.User.IsAppAdmin() {
			if visualModel.Visual.Category == nil || *visualModel.Visual.Category == -1 {
				mdl.Filter = &filter
			} else {
				// 可以获取所有应用
				mdl.Filter = &bson.M{
					"Category": visualModel.Visual.Category,
				}
			}
		} else {
			ids := []primitive.ObjectID{}
			if len(mdl.User.AppAuths) == 0 {
				return &model.PageResult{
					Records: []*Visual{},
					Total:   0,
					Size:    mdl.Page.Size,
				}, nil
			}
			for k := range mdl.User.AppAuths {
				bsonId, _ := primitive.ObjectIDFromHex(k)
				ids = append(ids, bsonId)
			}

			if visualModel.Visual.Category == nil || *visualModel.Visual.Category == -1 {
				mdl.Filter = &bson.M{
					"$and": bson.A{
						filter,
						bson.M{
							"ID": bson.M{
								"$in": ids,
							},
						},
					},
				}
			} else {
				mdl.Filter = &bson.M{
					"Category": visualModel.Visual.Category,
					"ID": bson.M{
						"$in": ids,
					},
				}
			}
		}

		if visualModel.Visual.OrgCategory != nil {
			f := *mdl.Filter
			f["orgCategory"] = visualModel.Visual.OrgCategory
		}

		visualRes, err := v.visualRepo.Find(mdl)
		if err != nil {
			return nil, err
		}
		if visualRes == nil {
			visualRes = []*Visual{}
		}

		visualList := visualRes.([]*Visual)
		cgsService := cgs.NewCgsService()
		for _, visual := range visualList {
			proj, err := cgsService.GetByPublishId(visual.GetId(), mdl.User)
			if err != nil {
				continue
			}
			sceneId, err := proj.GetSceneId()
			if err != nil {
				continue
			}
			visual.SceneID = sceneId
			visual.SetKey()
		}

		count, err := v.visualRepo.Count(mdl)
		res := &model.PageResult{
			Records: visualRes,
			Total:   count,
			Size:    mdl.Page.Size,
		}
		if mdl.Page.Size == 0 {
			res.Size = count
		}

		return res, nil
	} else {
		return nil, errors.New("VisualModel type error")
	}
}

func (v *visualService) ListAll(user *model.User) ([]*category.Vo, error) {
	_ = user
	categoryService := category.NewCategoryService()
	orgCategory, err := categoryService.ListAll("Org", user)
	if err != nil {
		return nil, err
	}
	if len(orgCategory) == 0 {
		return []*category.Vo{}, nil
	}

	type s struct {
		Categories []*category.Vo
		Visuals    []*Visual
	}

	visualTypes := []string{"App", "TwoD", "ThreeD", "Earth"}
	visualData := make(map[string]s)

	for _, visualType := range visualTypes {
		categoryList, err := categoryService.ListAll(visualType, user)
		if err != nil {
			return nil, err
		}
		all, err := v.listAll(visualType, user)
		if err != nil {
			return nil, err
		}
		visualData[visualType] = s{
			Categories: categoryList,
			Visuals:    all,
		}
	}

	orgList := make([]*category.Vo, 0)
	for _, org := range orgCategory {
		isOrgEmpty := true
		orgValue, err := strconv.ParseInt(org.CategoryValue, 10, 32)
		if err != nil {
			return nil, err
		}
		for _, visualType := range visualTypes {
			isVisualEmpty := true
			vc := category.Vo{
				ID:          primitive.ObjectID{},
				CategoryKey: visualType,
				VisualType:  visualType,
				Children:    make([]*category.Vo, 0),
			}
			for _, c := range visualData[visualType].Categories {
				isCatEmpty := true
				cc := category.Vo{
					ID:            c.ID,
					CategoryValue: c.CategoryValue,
					VisualType:    c.VisualType,
					CategoryKey:   c.CategoryKey,
				}
				for _, v := range visualData[visualType].Visuals {
					if !user.IsAppAdmin() && !user.HasVisualAuth(v.GetId()) {
						continue
					}
					parseInt, err := strconv.ParseInt(cc.CategoryValue, 10, 32)
					if err != nil {
						return nil, err
					}
					i32 := int32(-1)
					if v.Category == nil {
						v.Category = &i32
					}
					if *v.Category == int32(parseInt) && v.OrgCategory != nil && *v.OrgCategory == int32(orgValue) {
						isOrgEmpty = false
						isVisualEmpty = false
						isCatEmpty = false
						cc.VisualList = append(cc.VisualList, v)
					}
				}
				if !isCatEmpty {
					vc.Children = append(vc.Children, &cc)
				}
			}
			if !isVisualEmpty {
				org.Children = append(org.Children, &vc)
			}
		}
		if !isOrgEmpty {
			orgList = append(orgList, org)
		}
	}

	return orgList, nil
}

func (v *visualService) listAll(visualType string, user *model.User) ([]*Visual, error) {
	visualCollectionName := server.CgsPortalVisualCollectionName + visualType
	v.visualRepo.SetCollectionName(visualCollectionName)
	visualRes, err := v.visualRepo.List(&Visual{}, user)
	return visualRes, err
}

func (v *visualService) GetAllVisualAuths(tenantId string) (map[string][]string, error) {
	// 集合名
	visualCollectionName := server.CgsPortalVisualCollectionName + "ThreeD"
	//visualConfigCollectionName := server.CgsPortalVisualCollectionName + visualModel.VisualType + "Config"

	// 查询 visual
	v.visualRepo.SetCollectionName(visualCollectionName)
	visualList, err := v.visualRepo.FindByTenantId(tenantId)
	if err != nil {
		return nil, err
	}
	if len(visualList) == 0 {
		return nil, nil
	}
	appAuths := server.GetAllAppAuth()
	auths := []string{string(server.ManageAppAuth)}
	for i := range appAuths {
		auths = append(auths, appAuths[i].Name)
	}
	appAuth := make(map[string][]string, len(visualList))
	for i := range visualList {
		appAuth[visualList[i].ID.Hex()] = auths
	}
	return appAuth, nil
}

func (v *visualService) CheckVisual(user *model.User) error {
	// 1.查询所有的应用
	v.visualRepo.SetCollectionName(server.CgsPortalVisualCollectionName + "ThreeD")
	list, err := v.visualRepo.Find(&model.DataUserModel{
		Data: &Model{
			Visual: Visual{},
		},
		User:   user,
		Filter: &bson.M{},
	})
	if err != nil {
		return err
	}
	if list == nil {
		return model.NotFoundError("visual")
	}
	visualList := list.([]*Visual)

	// 2. 根据应用id检查是否有应用角色
	roleService := role.NewRoleService()
	for _, visual := range visualList {
		// 更新visual
		if visual.Owner == "" {
			if visual.CreateUser != "" {
				visual.Owner = visual.CreateUser
			} else if visual.UserId != "" {
				visual.Owner = visual.UserId
			}
			if visual.Owner == "" {
				return fmt.Errorf("no owner of %s", visual.ID.Hex())
			}
			err := v.visualRepo.UpdateOwner(&model.DataUserModel{
				Data: visual,
				User: user,
			})
			if err != nil {
				return err
			}
		}
		// 检查角色
		roleModel := &model.Role{
			VisualId: visual.ID.Hex(),
		}
		roleModel.UserId = visual.Owner
		err := roleService.CheckAppRoleByVisualId(&model.DataUserModel{
			Data: roleModel,
			User: user,
		})
		if err != nil {
			return err
		}
	}
	return nil
}
